package ms.maplestory.boot;

import ms.maplestory.boot.client.Client;
import ms.maplestory.boot.configs.BalloonConfig;
import ms.maplestory.boot.configs.ServerConfig;
import ms.maplestory.boot.configs.WorldConfig;
import ms.maplestory.boot.connection.crypto.Crypto;
import ms.maplestory.boot.connection.db.DatabaseManager;
import ms.maplestory.boot.connection.netty.ChannelAcceptor;
import ms.maplestory.boot.connection.netty.LoginAcceptor;
import ms.maplestory.boot.loaders.FieldData;
import ms.maplestory.boot.loaders.MainLoader;
import ms.maplestory.boot.loaders.StringData;
import ms.maplestory.boot.util.Loader;
import ms.maplestory.boot.util.ServerUtil;
import ms.maplestory.boot.util.SpringUtil;
import ms.maplestory.boot.util.Util;
import ms.maplestory.boot.util.container.Tuple;
import ms.maplestory.boot.world.ChannelServer;
import ms.maplestory.boot.world.LoginServer;
import ms.maplestory.boot.world.WorldServer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;


@Slf4j(topic = "[Server Main]")
@Service
@RequiredArgsConstructor
public class Server extends Properties {

    private final ServerConfig serverConfig;
    private List<WorldServer> worldServerList = new ArrayList<>();
    private List<BalloonConfig> balloonList = new ArrayList<>();

    public static Server getInstance() {
        return SpringUtil.getBean(Server.class);
    }

    public void init(String[] args) {
        if (Objects.isNull(serverConfig)) {
            throw new NullPointerException("Server config initialize error. /server.yml not found");
        }
        ServerUtil.init();
        log.info("Starting server.");
        StopWatch watch = new StopWatch();
        watch.start("DatabaseManager initialization");
        DatabaseManager.init();
        watch.stop();
        log.info("{} in {} ms", watch.getLastTaskName(), watch.getLastTaskTimeMillis());

        long startNow = System.currentTimeMillis();

        try {
            checkDat();
            loadWzData();
        } catch (IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
        StringData.loadStringFromDat();
        FieldData.loadWorldMapFromDat();

        Crypto.initialize(ServerConstants.VERSION);

        LoginAcceptor la = new LoginAcceptor();
        la.loginServer = LoginServer.getInstance();
        new Thread(la).start();

        serverConfig.getBalloons().forEach(balloonConfig -> {
            if (balloonConfig.isEnable()) {
                balloonList.add(balloonConfig);
            }
        });

        List<WorldServer> worldServers = serverConfig.getWorlds()
                                                     .stream()
                                                     .filter(WorldConfig::isEnable)
                                                     .map(world -> new WorldServer(world.getId(),
                                                 world.getName(),
                                                 world.getState(),
                                                 world.getEventDesc(),
                                                 world.getEventExpWse(),
                                                 world.getEventDropWse(),
                                                 world.isBlockCharCreation(),
                                                 world.getChannels(),
                                                 world.getAdultChannel(),
                                                 world.isRecommend(),
                                                 world.getRecommendMsg()))
                                                     .collect(Collectors.toList());
        worldServerList.addAll(worldServers);
        for (WorldServer worldServer : getWorlds()) {
            log.info("world:{}-{},Channel:{},blockCharCreation:{},adultChannel:{}",
                    worldServer.getId(), worldServer.getName(), worldServer.getChannelServers().size(), worldServer.isBlockCharCreation(), worldServer.getAdultChannel());
            for (ChannelServer channelServer : worldServer.getChannelServers()) {
                ChannelAcceptor ca = new ChannelAcceptor();
                ca.channelServer = channelServer;
                new Thread(ca).start();
            }
        }
        log.info(String.format("Finished loading server in %dms", System.currentTimeMillis() - startNow));

    }

    private void checkDat() {
        File file = new File(ServerConstants.DAT_DIR + "/equips");
        boolean exists = file.exists();
        if (!exists) {
            log.warn("Dat files cannot be found (at least not the equip dats).");
            System.exit(0);
        }
    }

    public void loadWzData() throws IllegalAccessException, InvocationTargetException {
        String datFolder = ServerConstants.DAT_DIR;
        for (Class c : MainLoader.data) {
            for (Method method : c.getMethods()) {
                String name;
                Loader annotation = method.getAnnotation(Loader.class);
                if (annotation != null) {
                    name = annotation.varName();
                    File file = new File(datFolder, name + ".dat");
                    boolean exists = file.exists();
                    long start = System.currentTimeMillis();
                    method.invoke(c, file, exists);
                    long total = System.currentTimeMillis() - start;
                    if (exists) {
                        log.info("WzInit Took {}ms to load from {}", total, file.getName());
                    } else {
                        log.info("WzInit Took {}ms to load using {}", total, method.getName());
                    }
                }
            }
        }
    }

    public List<BalloonConfig> getBalloon() {
        return balloonList;
    }

    public List<WorldServer> getWorlds() {
        return worldServerList;
    }

    public WorldServer getWorldById(int id) {
        return Util.findWithPred(getWorlds(), worldServer -> worldServer.getId() == id);
    }

    public Tuple<Integer, Client> getChannelFromTransfer(int charId, int worldId) {
        for (ChannelServer c : getWorldById(worldId).getChannelServers()) {
            if (c.getTransfers().containsKey(charId)) {
                return c.getTransfers().get(charId);
            }
        }
        return null;
    }
}